Skip to content

Spring MVC并发模型

Spring Controller的并发模型

单例Bean与多线程

  • Controller默认是单例:Spring容器中每个Controller只有一个实例
  • 方法是多线程并发执行:Servlet容器(如Tomcat)为每个请求分配独立的线程
  • 成员变量是线程共享的:需要注意线程安全问题
  • 局部变量是线程隔离的:每个线程有自己的栈空间

代码示例

java
@RestController
public class DemoController {
    // 线程共享的成员变量 - 存在线程安全问题
    private int counter = 0;
    
    // 线程隔离的局部变量
    @GetMapping("/test")
    public String test() {
        int localVar = 0;  // 每个线程都有自己的副本
        localVar++;
        
        // 这里的counter需要同步
        synchronized(this) {
            counter++;
        }
        
        return "Count: " + counter;
    }
}

线程执行流程

graph TD
    A[客户端请求1] --> B[Tomcat线程池]
    A2[客户端请求2] --> B
    A3[客户端请求3] --> B
    
    B --> C[分配线程1]
    B --> D[分配线程2]
    B --> E[分配线程3]
    
    C --> F[执行Controller方法]
    D --> F
    E --> F
    
    F --> G[返回响应]
image-20260508104144143

Servlet容器的线程模型

Tomcat线程池配置

yaml
# application.yml
server:
  tomcat:
    threads:
      max: 200        # 最大线程数
      min-spare: 10   # 最小空闲线程
    max-connections: 10000

请求处理流程

请求到达 → Tomcat Acceptor接收 → 放入连接队列
        → 从线程池获取工作线程 → 执行Spring Controller方法
        → 返回响应 → 线程归还线程池

并发场景下的锁使用策略

需要加锁的场景

场景是否需要锁原因
修改共享成员变量多线程同时修改导致数据不一致
防止重复远程调用避免缓存击穿,减少资源浪费
数据库更新操作数据库事务隔离,但应用层也需要考虑
幂等性控制防止重复提交

锁粒度的选择

// 1. 方法级锁 - 最简单,但并发度低
public synchronized Result method() { }

// 2. 对象级锁 - 中等粒度
private final Object lock = new Object();
synchronized(lock) { }

// 3. 分段锁 - 提高并发
private final Object[] segmentLocks = new Object[16];
synchronized(segmentLocks[id.hashCode() % 16]) { }

// 4. 细粒度锁 - 最高并发
private final ConcurrentHashMap<Long, Object> locks = new ConcurrentHashMap<>();
synchronized(locks.computeIfAbsent(id, k -> new Object())) { }

分段锁初始化:

java
private final Object[] segmentLocks = new Object[16];
// 必须先初始化数组中的每一个锁对象!
{
    for (int i = 0; i < segmentLocks.length; i++) {
        segmentLocks[i] = new Object();
    }
}

// 通过hash和位运算,获取到锁
synchronized(segmentLocks[(id.hashCode() & Integer.MAX_VALUE) & 15]) { }

实际应用中的并发控制

缓存击穿防护模式

java
@Component
public class ConcurrentControlService {

    private final ConcurrentHashMap<String, Future<?>> locks = new ConcurrentHashMap<>();

    public <T> T execute(String key, Supplier<T> supplier) {
        // 双重检查避免重复执行
        T result = getFromCache(key);
        if (result != null) {
            return result;
        }

        synchronized (getLock(key)) {
            result = getFromCache(key);
            if (result != null) {
                return result;
            }

            // 执行实际逻辑
            result = supplier.get();
            saveToCache(key, result);
            return result;
        }
    }
    
    private Object getLock(String key) {
        return locks.computeIfAbsent(key, k -> new Object());
    }
}

Spring Cache的并发控制

java
@Configuration
@EnableCaching
public class CacheConfig {
    
    @Bean
    public CacheManager cacheManager() {
        ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
        cacheManager.setCacheNames(Arrays.asList("users", "orders"));
        return cacheManager;
    }
}

@Service
public class OrderService {
    
    // sync=true 确保只有一个线程执行方法体
    @Cacheable(value = "orders", key = "#id", sync = true)
    public Order getOrder(Long id) {
        // 只会有一个线程执行这里
        return fetchFromDatabase(id);
    }
}

避免并发时的重复提交

java
@PostMapping("/submit")
@Transactional
public Result submit(@RequestBody Request request) {
    // 使用分布式锁或数据库唯一约束
    String lockKey = "submit:" + request.getId();
    if (!redisLock.tryLock(lockKey, 10, TimeUnit.SECONDS)) {
        throw new BusinessException("请勿重复提交");
    }
    
    try {
        // 业务逻辑
    } finally {
        redisLock.unlock(lockKey);
    }
}

浏览器并发请求限制

HTTP/1.1的并发限制

浏览器HTTP/1.1 同域名并发数HTTP/2 并发数
Chrome6无限制(多路复用)
Firefox6无限制
Safari6无限制
Edge6无限制

浏览器队列机制

// 超过并发数的请求会被放入队列
// 假设并发限制为6
1-6个请求 → 立即发送
第7个请求 → 等待,直到有请求完成释放连接
第8个请求 → 继续等待...

突破限制的方法

<!-- 1. 使用不同子域名 -->
<img src="https://static1.example.com/a.jpg">
<img src="https://static2.example.com/b.jpg">

<!-- 2. 使用HTTP/2 -->
<!-- HTTP/2支持多路复用,无并发限制 -->

<!-- 3. 使用CDN分散域名 -->